#include "../Misc/part3_shared.h"

int main(int argc, char **argv) {

	DEHT *ht;
	char** lexicon;
	LONG_INDEX_PROJ lexicon_size = 0;
	RULE rule;
	int status;
	LONG_INDEX_PROJ *seeds = NULL;
	LONG_INDEX_PROJ i,j;
	INI_PARAMS ini_params;
	LONG_INDEX_PROJ num_of_chains;
	char *curHash;
	LONG_INDEX_PROJ k = 0;
	char firstPass[MAX_PASSWORD_LENGTH];
	char newPass[MAX_PASSWORD_LENGTH];
	BasicHashFunctionPtr crypt_func;
	int func_buffer_length;
		
	if (argc != 2)
	{
		fprintf(stderr, "Error: Usage create_rainbow_table <preferences filename>\n");
		exit(1);
	}

	/* load .ini file to struct */
	ini_params = get_params_from_INI_file(argv[1]);
	/* check params */
	if (!strcmp(ini_params.HashFunction, "MD5")) {
		crypt_func = MD5BasicHash;
		func_buffer_length = MD5_OUTPUT_LENGTH_IN_BYTES;
	} else if (!strcmp(ini_params.HashFunction, "SHA1")) {
		crypt_func = SHA1BasicHash;
		func_buffer_length = SHA1_OUTPUT_LENGTH_IN_BYTES;
	} else {
		fprintf(stderr, "Error: Hash %s is not supported\n", argv[1]);
		exit(1);
	}

	if (ini_params.ChainLength < 1) {
		fprintf(stderr, "Error: rainbow_chain_length in %s is %d. It should be positive number\n",
				argv[1], ini_params.ChainLength);
		exit(1);
	}

	if (ini_params.NumOfHashEntries < 1) {
		fprintf(stderr, "Error: entries_in_hash_table in %s is %d. It should be a positive number\n",
				argv[1], ini_params.NumOfHashEntries);
		exit(1);
	}

	if (ini_params.ElementsInBucket < 1) {
		fprintf(stderr, "Error: bucket_block_length in %s is %d. It should be a positive number\n",
				argv[1], ini_params.ElementsInBucket);
		exit(1);
	}

 	lexicon = load_lexicon(ini_params.LexiconName, &lexicon_size);
	status = create_rule(&rule, ini_params.Rule, lexicon, lexicon_size);
	if (status == -1) {
		free_lexicon(lexicon, lexicon_size);
		exit(1);
	} 

	/* create seed and DEHT */
	ht = create_empty_DEHT(ini_params.OutputFilePrefix,cryptHashToTable,cryptHashTo64bit,ini_params.HashFunction,ini_params.NumOfHashEntries,
						   ini_params.ElementsInBucket,BYTES_PER_VALIDATION_KEY);	
	if (ht == NULL)
	{
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}
	
	status = calc_DEHT_last_block_per_bucket(ht);
	if (status == DEHT_STATUS_FAIL){
		erase_DEHT_file(ht);
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}

	seeds = (LONG_INDEX_PROJ*) malloc( ini_params.ChainLength * sizeof(LONG_INDEX_PROJ));
	if (seeds == NULL){
		fprintf(stderr, "Error: not enough memory\n");
		erase_DEHT_file(ht);
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}

	for (i = 0 ; i < ini_params.ChainLength;  i++){
		seeds[i] = pseudo_random_function((unsigned char *)ini_params.MainRandSeed, strlen(ini_params.MainRandSeed), i); 
	}

	status = write_DEHT_Seed(ht, seeds, ini_params.ChainLength);
	if (status == DEHT_STATUS_FAIL)
	{
		free(seeds);
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		erase_DEHT_file(ht);
		exit(1);
	}

	/* create chains and add to deht */
	num_of_chains = (rule.number_of_possible_passwords * 10 ) / ini_params.ChainLength;
	
	curHash = (char *)malloc(func_buffer_length * sizeof(char));
	if (curHash== NULL)
	{
		fprintf(stderr, "Error: not enough memory\n");
		free(seeds);
		erase_DEHT_file(ht);
		free_lexicon(lexicon, lexicon_size);
		free_rule(&rule);
		exit(1);
	}
	
	for (i = 0; i < num_of_chains; i ++)
	{
		k = pseudo_random_function((unsigned char*)ini_params.MainRandSeed, strlen(ini_params.MainRandSeed), i) % rule.number_of_possible_passwords;
		generate_kth_password(k, firstPass, rule);
		cryptHash(crypt_func, firstPass, (unsigned char*)curHash);

		for (j = 0; j < ini_params.ChainLength; j++)
		{
			k = pseudo_random_function((unsigned char*)curHash, func_buffer_length, seeds[j]) % rule.number_of_possible_passwords;
			generate_kth_password(k, newPass, rule);
			
			cryptHash(crypt_func, newPass, (unsigned char*)curHash );
		}

		status = add_DEHT(ht,(unsigned char *)curHash, func_buffer_length, (unsigned char *)firstPass, strlen(firstPass));	
		if (status == DEHT_STATUS_FAIL) {
			free(seeds);
			erase_DEHT_file(ht);
			free_lexicon(lexicon, lexicon_size);
			free_rule(&rule);
			free(curHash);
			exit(1);
		}
	}

free(seeds);
lock_DEHT_files(ht);
free_lexicon(lexicon, lexicon_size);
free_rule(&rule);
free(curHash);

return 0;
}
